/* Archive Extractor
 * Copyright 2008-2009 Matthew Hoops
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $URL: http://archive-extractor.googlecode.com/svn/trunk/zip.cpp $
 * $Id: zip.cpp 2 2009-03-23 01:29:43Z clone2727 $
 *
 */

#include "zip.h"
#include <zlib.h>

ZipArchive::ZipArchive() : StandardArchive() {
}

std::vector<const char*> ZipArchive::getArchiveExtensions() {
	std::vector<const char*> exList;
	exList.push_back("zip");
	return exList;
}

bool ZipArchive::extractFiles(FILE* file) {
	for (;;) {
		uint32 tag = readUint32LE(file);
		if (tag == 0x08064b50) { // Archive extra data record
			uint32 dataSize = readUint32LE(file);
			fseek(file, dataSize, SEEK_CUR);
		} else if (tag == 0x04034b50) { // Local File Header
			fseek(file, 2, SEEK_CUR);
			uint16 gpbFlag = readUint16LE(file);
			uint16 compressionMethod = readUint16LE(file);
			fseek(file, 8, SEEK_CUR);
			uint32 compressedSize = readUint32LE(file);
			uint32 uncompressedSize = readUint32LE(file);
			uint16 fileNameLength = readUint16LE(file);
			uint16 extraFieldLength = readUint16LE(file);
			
			char* fileName = new char[fileNameLength + 1];
			fread(fileName, 1, fileNameLength, file);
			fileName[fileNameLength] = 0;
			
			fseek(file, extraFieldLength, SEEK_CUR);
			
			byte* compressedData = (byte *)malloc(compressedSize);
			fread(compressedData, 1, compressedSize, file);
			
			if (!extractToFile(fileName, compressionMethod, compressedSize, compressedData, uncompressedSize))
				return false;
			
			if (gpbFlag & (1 << 3)) {
				fseek(file, 4, SEEK_CUR);
				compressedSize = readUint32LE(file);
				fseek(file, 4 + compressedSize, SEEK_CUR);
			}
		} else if (tag == 0x02014b50) {	// Central Directory
			//printf ("Found central directory!\n");
			break;
		} else {
			return false;
		}
	}
 
	return true;
}

bool ZipArchive::extractToFile(char* filename, uint16 compressionMethod, uint32 compressedSize, byte *compressedData, uint32 uncompressedSize) {
	printf ("Extracting file \'%s\'...\n", filename);
	
	FILE *output = fopen(filename, "wb");
	if (!output) {
		printf ("Could not open for writing!\n");
		return false;
	}
	
	if (compressionMethod == 0) {
		// Uncompressed
		fwrite(compressedData, 1, compressedSize, output);
#ifdef ENABLE_ZLIB
	} else if (compressionMethod == 8) {
		// DEFLATE compressed
		byte *outData = new byte[uncompressedSize];
		
		z_stream strm;
		strm.zalloc = Z_NULL;
		strm.zfree = Z_NULL;
		strm.opaque = Z_NULL;
		strm.avail_in = compressedSize;
		strm.next_in = compressedData;
		
		// Negative windows bits means there is no zlib header present in the data.
		int zResult = inflateInit2(&strm, -MAX_WBITS);
		if (zResult != Z_OK) {
			printf ("Failed to init inflation\n");
			return false;
		}
		
		strm.avail_out = uncompressedSize;
		strm.next_out = outData;
		
		zResult = inflate(&strm, Z_SYNC_FLUSH);
		if (zResult != Z_OK && zResult != Z_STREAM_END) {
			printf ("Failed to inflate (%d)\n", zResult);
			return false;
		}
		
		fwrite(outData, 1, uncompressedSize, output);
#endif
	} else {
		printf ("Unknown/unhandled compression %d\n", compressionMethod);
		return false;
	}
	
	fflush(output);
	fclose(output);
	
	return true;
}
